﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Reflection.Emit;

namespace Kokomo
{
	/// <summary>
	/// Provides extension methods for reflection objects.
	/// </summary>
	public static class ReflectionExtensions
	{
		public static int SizeOf(this Type type)
		{
			ArgumentValidation.CheckNullArgument("type", type);

			return Marshal.SizeOf(type);
		}
		public static Type[] GetAllTypes(this Assembly assembly)
		{
			ArgumentValidation.CheckNullArgument("assembly", assembly);

			Type[] types = null;
			try
			{
				types = assembly.GetTypes();
			}
			catch (ReflectionTypeLoadException ex)
			{
				types = ex.Types.Where(type => type != null).ToArray();
			}

			if (types == null)
			{
				types = new Type[0];
			}

			return types;
		}
		public static IEnumerable<TValue> GetStaticValues<TValue>(this Type type)
		{
			ArgumentValidation.CheckNullArgument("type", type);

			List<TValue> values = new List<TValue>();
			PropertyInfo[] properties = type.GetProperties(BindingFlags.Static | BindingFlags.Public);
			foreach (var property in properties)
			{
				if (typeof(TValue).IsAssignableFrom(property.PropertyType))
				{
					TValue value = (TValue)property.GetValue(null, null);
					values.Add(value);
				}
			}
			FieldInfo[] fields = type.GetFields(BindingFlags.Static | BindingFlags.Public);
			foreach (var field in fields)
			{
				if (typeof(TValue).IsAssignableFrom(field.FieldType))
				{
					TValue value = (TValue)field.GetValue(null);
					values.Add(value);
				}
			}

			return values;
		}
		public static ILookup<TValue, TMember> ScanMembers<TMember, TAttribute, TValue>(this Type type, BindingFlags bindingAttr, Func<TAttribute, TValue> selector)
			where TMember : MemberInfo
			where TAttribute : Attribute
		{
			ArgumentValidation.CheckNullArgument("type", type);
			ArgumentValidation.CheckNullArgument("selector", selector);

			var stuff = type.GetMembers(bindingAttr).OfType<TMember>()
				.SelectMany(member => member.GetCustomAttributes<TAttribute>(), (member, attribute) => new { Member = member, value = selector(attribute) })
				.ToLookup(pair => pair.value, pair => pair.Member);

			return stuff;
		}
		public static MethodInfo FindImplicitConversion(this Type sourceType, Type targetType)
		{
			ArgumentValidation.CheckNullArgument("sourceType", sourceType);
			ArgumentValidation.CheckNullArgument("targetType", targetType);

			List<MethodInfo> converters = new List<MethodInfo>();
			converters.AddRange(sourceType.GetMethods(BindingFlags.Static | BindingFlags.Public));
			converters.AddRange(targetType.GetMethods(BindingFlags.Static | BindingFlags.Public));
			MethodInfo converter = null;
			foreach (var method in converters)
			{
				ParameterInfo[] parameters = method.GetParameters();
				if (
					(method.Name == "op_Implicit")
					&& (method.ReturnType == targetType)
					&& (parameters.Length == 1)
					&& (parameters[0].ParameterType.IsAssignableFrom(sourceType))
					)
				{
					converter = method;
					break;
				}
			}

			return converter;
		}
		public static TDelegate CreateDelegate<TDelegate>(this MethodInfo method)
			where TDelegate : class
		{
			TDelegate d = (TDelegate)(object)Delegate.CreateDelegate(typeof(TDelegate), method);
			return d;
		}
		//public static TDelegate CreateDelegate<TDelegate>(this MethodInfo method, object thisArgument, bool cast)
		//    where TDelegate : class
		//{
		//    ArgumentValidation.CheckNullArgument("method", method);

		//    if (!cast)
		//    {
		//        TDelegate d = (TDelegate)(object)Delegate.CreateDelegate(typeof(TDelegate), thisArgument, method);
		//        return d;
		//    }
		//    else
		//    {
		//        Type delegateType = typeof(TDelegate);
		//        if (!typeof(Delegate).IsAssignableFrom(delegateType))
		//        {
		//            throw new ArgumentException("Invalid delegate type parameter", "TDelegate");
		//        }
		//        MethodInfo invokeMethod = delegateType.GetMethod("Invoke");

		//        ParameterInfo[] delegateParameters = invokeMethod.GetParameters();
		//        ParameterInfo[] methodParameters = invokeMethod.GetParameters();
		//        List<Type> parameterTypes = new List<Type>();
		//        int argumentOffset = 0;
		//        if (!method.IsStatic)
		//        {
		//            parameterTypes.Add(method.ReturnType);
		//            argumentOffset = 1;
		//        }
		//        parameterTypes.AddRange(delegateParameters.Select(p => p.ParameterType));
		//        if (delegateParameters.Length != methodParameters.Length)
		//        {
		//            throw new ArgumentException("Cannot bind target method");
		//        }

		//        DynamicMethod shim = new DynamicMethod("shim_" + method.Name, invokeMethod.ReturnType, parameterTypes.ToArray());
		//        ILGenerator il = shim.GetILGenerator();
		//        if (!method.IsStatic)
		//        {
		//            shim.DefineParameter(1, ParameterAttributes.None, "this");
		//        }
		//        for (int i = 0; i < methodParameters.Length; i++)
		//        {
		//            ParameterInfo delegateParameter = delegateParameters[i + argumentOffset];
		//            ParameterInfo methodParameter = methodParameters[i];

		//            shim.DefineParameter(i + argumentOffset + 1, delegateParameter.Attributes, delegateParameter.Name);

		//            il.Emit(OpCodes.Ldarg, i);
		//            if (delegateParameter.ParameterType != methodParameter.ParameterType)
		//            {
		//                il.Emit(OpCodes.Castclass, methodParameter.ParameterType);
		//            }
		//        }

		//        il.EmitCall(method);

		//        if (method.ReturnType != typeof(void))
		//        {
		//            if (invokeMethod.ReturnType != method.ReturnType)
		//            {
		//                il.Emit(OpCodes.Castclass, invokeMethod.ReturnType);
		//            }
		//        }
		//        il.Emit(OpCodes.Ret);

		//        TDelegate d = method.CreateDelegate<TDelegate>(thisArgument, false);
		//    }
		//}
	}
}
